package core;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.util.Iterator;

import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.BCPGOutputStream;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPOnePassSignature;
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.PGPSignatureList;
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.bouncycastle.openpgp.PGPUtil;

/** TODO: Other kind of keys should be accepted. Right now we just work with
 * DSA. **/
/**
 * This class is just a package of utilities to make easy the use of Keys of
 * the java.security standard library.
 * 
 * @author ole
 */
public class KeyUtils {
//	// Constants of this package.
//	public static final String KEY_ALGORITHM = "DSA";
//	public static final String RANDGEN_ALGORITHM = "SHA1PRNG";
//	public static final int KEYSIZE = 1024;
//	public static final String KEY_EXTENSION = "key";
//	public static final String PUBLICKEY_SUFFIX = "-public" + "." + KEY_EXTENSION;
//	public static final String PRIVATEKEY_SUFFIX = "-private" + "." + KEY_EXTENSION;
//	
//	/*
//	 * Public functions of the package.
//	 */
//	
//	/**
//	 * Create a pair of keys.
//	 * 
//	 * @return:
//	 * 		A KeyPair instance that will contains a PublicKey and a PrivateKey.
//	 * 		Returns null if there are problems.
//	 */
//	public static KeyPair createNewKeyPair() {
//		KeyPairGenerator kpg;
//		SecureRandom randgen;
//		
//		try {
//			// Initialize the generator.
//			kpg = KeyPairGenerator.getInstance(KEY_ALGORITHM);
//			randgen = SecureRandom.getInstance(RANDGEN_ALGORITHM);
//			kpg.initialize(KEYSIZE, randgen);
//			
//			// Create and return the pair.
//			return kpg.generateKeyPair();
//		}
//		// This code should never be reached. We are using a standard
//		// algorithm (DSA).
//		catch (NoSuchAlgorithmException e) {
//			e.printStackTrace();
//		}
//		
//		return null;
//	}
//	
//	/**
//	 * The files this module is creating has the form <name>-private.key for
//	 * private keys and <name>-public.key for public keys. This function helps
//	 * to get the name part from a filename.
//	 * 
//	 * @return:
//	 * 		A String representing the name part of the filename. It returns
//	 * 		null if the String passed has not the format requested.
//	 */
//	public static String getNameFromKeyFilename(String fname) {
//		File fd;
//		String last_fname, suffix;
//		
//		// Check that the filename has the format requested (and take the
//		// suffix.
//		if (fname.endsWith(PRIVATEKEY_SUFFIX) ||
//			fname.endsWith(PUBLICKEY_SUFFIX))
//			suffix = PRIVATEKEY_SUFFIX;
//		else
//			return null;
//		
//		// This is a easy way to permit paths to files in this function and
//		// take just the filename part.
//		fd = new File(fname);
//		last_fname = fd.getName();
//		
//		return last_fname.substring(0, last_fname.indexOf(suffix));
//	}
//	
//	/** TODO: The file could be ciphered. This should permit accept a
//	 * password. **/
//	/**
//	 * Recover a PrivateKey from a file. The format of that key must be PKCS8.
//	 * 
//	 * @param fname:
//	 * 		The filename of the file containing the private key.
//	 * @return:
//	 * 		The PrivateKey contained in the file or null if there are problems.
//	 * @throws FileNotFoundException:
//	 * 		Thrown if the file doesn't exists.
//	 * @throws InvalidKeySpecException:
//	 * 		Thrown if the key specification in the file is not in PKCS8.
//	 */
//	public static PrivateKey recoverPrivateKey(String fname)
//	throws FileNotFoundException, InvalidKeySpecException {
//		File fd;
//		FileInputStream fis;
//		EncodedKeySpec privkey_encoded;
//		KeyFactory kf;
//		byte encoded_key[];
//		
//		// Check that the file exists.
//		fd = new File(fname);
//		if (!fd.exists())
//			throw new FileNotFoundException("File " + fname +
//											" doesn't exists.");
//		
//		// Recover the bytearray with the key.
//		fis = new FileInputStream(fd);
//        encoded_key = Util.recoverDatafromInputStream(fis, (int)fd.length());
//        
//        /** TODO: Here we have a problem, we don't know how the key is
//         * encoded (its format, PKCS8, X.509...). It should be studied how
//         * to use any kind of key or how to distinguish the format. **/
//        
//        // Create the private key from the bytearray.
//        privkey_encoded = new PKCS8EncodedKeySpec(encoded_key);
//        try {
//        	kf = KeyFactory.getInstance(KEY_ALGORITHM);
//        	return kf.generatePrivate(privkey_encoded);
//        }
//        // This code should never be reached. We are using a standard
//        // algorithm.
//        catch (NoSuchAlgorithmException e) {
//        	e.printStackTrace();
//        }
//        
//        return null;
//	}
//	
//	/**
//	 * Recover a PrivateKey from a file. The format of that key must be PKCS8.
//	 * 
//	 * @param fname:
//	 * 		The filename of the file containing the private key.
//	 * @return:
//	 * 		The PrivateKey contained in the file or null if there are problems.
//	 * @throws FileNotFoundException:
//	 * 		Thrown if the file doesn't exists.
//	 * @throws InvalidKeySpecException:
//	 * 		Thrown if the key specification in the file is not in PKCS8.
//	 */
//	public static PublicKey recoverPublicKey(String fname) throws FileNotFoundException, InvalidKeySpecException {
//		File fd;
//		FileInputStream fis;
//		EncodedKeySpec pubkey_encoded;
//		KeyFactory kf;
//		byte encoded_key[];
//		
//		// Check that the file exists.
//		fd = new File(fname);
//		if (!fd.exists())
//			throw new FileNotFoundException("File " + fname +
//											" doesn't exists.");
//		
//		// Recover the bytearray with the key.
//		fis = new FileInputStream(fd);
//        encoded_key = Util.recoverDatafromInputStream(fis, (int)fd.length());
//        
//        /** TODO: Here we have a problem, we don't know how the key is
//         * encoded (its format, PKCS8, X.509...). It should be studied how
//         * to use any kind of key or who to distinguish the format. **/
//        
//        // Create the private key from the bytearray.
//        pubkey_encoded = new X509EncodedKeySpec(encoded_key);
//        try {
//        	kf = KeyFactory.getInstance(KEY_ALGORITHM);
//        	return kf.generatePublic(pubkey_encoded);
//        }
//        // This code should never be reached. We are using a standard
//        // algorithm.
//        catch (NoSuchAlgorithmException e) {
//        	e.printStackTrace();
//        }
//        
//        return null;
//	}
//	
//	/**
//	 * Function that serializes a Key into a file.
//	 * 
//	 * @param key:
//	 * 		The Key to serialize.
//	 * @param prefix:
//	 * 		The prefix the filename must has.
//	 * @return:
//	 * 		The name of the file created.
//	 * @throws IOException:
//	 * 		Raised if there are problems creating the file.
//	 */
//	public static String saveKey(Key key, String prefix, String path)
//	throws IOException {
//		File fd;
//		FileOutputStream fos;
//		String suffix, checked_path, fname;
//		byte key_enc[];
//		
//		// Build the name for the key.
//		checked_path = Util.checkDirectoryPath(path);
//		suffix = key instanceof PublicKey? PUBLICKEY_SUFFIX: PRIVATEKEY_SUFFIX;
//		fname = checked_path + prefix + suffix;
//		
//		// Check that there is not another file with the same name.
//		fd = new File(fname);
//		if (fd.exists())
//			throw new RuntimeException("Cannot serialize into " + fname +
//									   ". There is keys with that name.");
//		
//		// Check that the key can be encoded.
//		key_enc = key.getEncoded();
//		if (key_enc == null)
//			throw new RuntimeException("Key cannot be encoded.");
//		
//		// Create the file and write the encoded key to it.
//		fd.createNewFile();
//		fos = new FileOutputStream(fd);
//		fos.write(key_enc);
//		fos.close();
//		
//		return fname;
//	}
	
	/**************************************************************************************/
	
//	private static byte[] getLineSeparator() {
//        String nl = System.getProperty("line.separator");
//        byte[] nlBytes = new byte[nl.length()];
//
//        for (int i = 0; i != nlBytes.length; i++)
//            nlBytes[i] = (byte)nl.charAt(i);
//
//        return nlBytes;
//    }
//	
//	private static int getLengthWithoutSeparatorOrTrailingWhitespace(byte[] line) {
//        int    end = line.length - 1;
//
//        while (end >= 0 && isWhiteSpace(line[end]))
//            end--;
//
//        return end + 1;
//    }
//	
//	private static int readPassedEOL(ByteArrayOutputStream baos,
//			int last_ch, InputStream is)
//	throws IOException {
//		int lookahead;
//
//		lookahead = is.read();
//		if (last_ch == '\r' && lookahead == '\n') {
//			baos.write(lookahead);
//			lookahead = is.read();
//		}
//
//		return lookahead;
//	}
//
//	private static int readInputLine(ByteArrayOutputStream baos,
//			InputStream is)
//	throws IOException {
//		int lookahead;
//		int ch;
//
//		baos.reset();
//		lookahead = -1;
//		while ((ch = is.read()) >= 0) {
//			baos.write(ch);
//			if (ch == '\r' || ch == '\n') {
//				lookahead = readPassedEOL(baos, ch, is);
//				break;
//			}
//		}
//
//		return lookahead;
//	}
//
//	private static int readInputLine(ByteArrayOutputStream baos,
//			int lookahead, InputStream is)
//	throws IOException {
//		int ch;
//
//		baos.reset();
//		ch = lookahead;
//		do {
//			baos.write(ch);
//			if (ch == '\r' || ch == '\n') {
//				lookahead = readPassedEOL(baos, ch, is);
//				break;
//			}
//		}
//		while ((ch = is.read()) >= 0);
//
//		if (ch < 0)
//			lookahead = -1;
//
//		return lookahead;
//	}
//
//	/**
//	 * Check if the byte is line end character.
//	 * 
//	 * Thanks to The Bouncy Castle Legion for this code.
//	 * 
//	 * @param b:
//	 * 		The byte to be checked.
//	 * @return:
//	 * 		True if the byte passed is '\r' or '\n', false elsewhere.
//	 */
//	private static boolean isLineEnding(byte b) {
//		return b == '\r' || b == '\n';
//	}
//
//	/**
//	 * Check if the byte is a white space character.
//	 * 
//	 * Thanks to The Bouncy Castle Legion for this code.
//	 * 
//	 * @param b:
//	 * 		The byte to be checked.
//	 * @return:
//	 * 		True if the byte passed is ' ', '\t', '\r' or '\n', false
//	 * 		elsewhere.
//	 */
//	private static boolean isWhiteSpace(byte b) {
//		return isLineEnding(b) || b == '\t' || b == ' ';
//	}
//
//	/**
//	 * Returns the length that would have this line without any trailing
//	 * white space. Note that if it hasn't any trailing white space line.length
//	 * will be returned. 
//	 * 
//	 * Thanks to The Bouncy Castle Legion for this code.
//	 * 
//	 * @param line:
//	 * 		The line to be processed.
//	 * @return:
//	 * 		The length of the line without any trailing whitespace.
//	 */
//	private static int getLengthWithoutWhiteSpace(byte[] line) {
//		int end = line.length - 1;
//
//		while (end >= 0 && isWhiteSpace(line[end]))
//			end--;
//
//		return end + 1;
//	}
//
//	/**
//	 * Adds the line to the PGPSignatureGenerator in a proper manner and also
//	 * adds it to the OutputStream.
//	 * 
//	 * Thanks to The Bouncy Castle Legion for this code.
//	 * 
//	 * @param os:
//	 * 		The OutputStream where to adds in clear text this line.
//	 * @param sig_gen:
//	 * 		The PGPSignatureGenerator where the line will be added in a proper
//	 * 		manner.
//	 * @param line:
//	 * 		The line to be added.
//	 * @throws SignatureException
//	 * @throws IOException
//	 */
//	private static void processLine(OutputStream os,
//			PGPSignatureGenerator sig_gen,
//			byte[] line)
//	throws SignatureException, IOException {
//		// The Bouncy Castle Legion dixit:
//		// note: trailing white space needs to be removed from the end of
//		// each line for signature calculation RFC 4880 Section 7.1
//		int length = getLengthWithoutWhiteSpace(line);
//
//		// If there were line add it to the signature generator.
//		if (length > 0)
//			sig_gen.update(line, 0, length);
//
//		// Also write this line to the clear text in the output but with all
//		// its ending white spaces.
//		os.write(line, 0, line.length);
//	}
	
//	/**
//	 * Adds the line to the PGPSignature in a proper manner.
//	 * 
//	 * Thanks to The Bouncy Castle Legion for this code.
//	 * 
//	 * @param sig:
//	 * 		The PGPSignature where the line will be added in a proper manner.
//	 * @param line:
//	 * 		The line to be added.
//	 * @throws SignatureException
//	 * @throws IOException
//	 */
//	private static void processLine(PGPSignature sig, byte[] line)
//	throws SignatureException, IOException {
//		int length = getLengthWithoutWhiteSpace(line);
//		if (length > 0)
//			sig.update(line, 0, length);
//	}

	/**
	 * Sign a file in cleartext mode. It will create another file with the same
	 * name plus ".asc" at the end.
	 * 
	 * Thanks to The Bouncy Castle Legion for most of this code.
	 * 
	 * @param fname:
	 * 		The filename of the file to sign.
	 * @param secret:
	 * 		The secret key containing the public and private keys.
	 * @param pass:
	 * 		The password to get access to the private key.
	 * @throws NoSuchProviderException
	 * @throws PGPException
	 * @throws NoSuchAlgorithmException
	 * @throws IOException
	 * @throws SignatureException
	 */
	// Avoid the iterator from give warnings.
	@SuppressWarnings("unchecked")
	public static void signFile(String fname, PGPSecretKey secret, String pass)
	throws NoSuchProviderException, PGPException, NoSuchAlgorithmException,
	IOException, SignatureException {
		PGPSignatureGenerator sig_gen;
		PGPSignatureSubpacketGenerator sp_gen;
		PGPPrivateKey priv_key;
		PGPPublicKey pub_key;
		Iterator userid_iter;
		FileInputStream fis;
		FileOutputStream fos;
		ArmoredOutputStream aos;
		BCPGOutputStream bos;
		PGPLiteralDataGenerator ldata_gen;
		File fd;
		OutputStream os;
		int digest = PGPUtil.SHA256, ch;

		// Get both public and private keys from the secret.
		priv_key = secret.extractPrivateKey(pass.toCharArray(), "BC");
		pub_key = secret.getPublicKey();

		// Prepare the signature generator and the subpacket signature generator.
		sp_gen = new PGPSignatureSubpacketGenerator();
		sig_gen = new PGPSignatureGenerator(pub_key.getAlgorithm(), digest, "BC");
		sig_gen.initSign(PGPSignature.BINARY_DOCUMENT, priv_key);

		// If the public key has a UserID use it to sign every subpacket.
		userid_iter = pub_key.getUserIDs();
		if (userid_iter.hasNext()) {
			sp_gen.setSignerUserID(false, (String)userid_iter.next());
			sig_gen.setHashedSubpackets(sp_gen.generate());
		}

		// Open the input and output.
		fd = new File(fname);
		fis = new FileInputStream(fname);
		fos = new FileOutputStream(fname + ".asc");
		aos = new ArmoredOutputStream(fos);
		bos = new BCPGOutputStream(aos);
		
		ldata_gen = new PGPLiteralDataGenerator();
		sig_gen.generateOnePassVersion(false).encode(bos);
		os = ldata_gen.open(bos, PGPLiteralData.BINARY, fd);
		while ((ch = fis.read()) >= 0) {
            os.write(ch);
            sig_gen.update((byte)ch);
        }
		
		ldata_gen.close();
		sig_gen.generate().encode(bos);
		aos.close();
		fis.close();
		fos.close();
		
//		aos.beginClearText(digest);
//
//		// Process one line at a time. Here we are adding the clear text to
//		// the signed file.
//		line_out = new ByteArrayOutputStream();
//		lookahead = readInputLine(line_out, fis);
//		processLine(aos, sig_gen, line_out.toByteArray());
//		if (lookahead != -1) {
//			do {
//				lookahead = readInputLine(line_out, lookahead, fis);
//
//				sig_gen.update((byte)'\r');
//				sig_gen.update((byte)'\n');
//				processLine(aos, sig_gen, line_out.toByteArray());
//			}
//			while (lookahead != -1);
//		}	
//
//		// End clear text.
//		fis.close();
//		aos.endClearText();
//
//		// Add the sign and the hash to the file.
//		bos = new BCPGOutputStream(aos);
//		sig_gen.generate().encode(bos);
//		aos.close();
	}
	
//	public static boolean verifyFile(InputStream in, PGPPublicKey key,
//								   	 String resultName)
//	throws Exception {
//		ArmoredInputStream aIn = new ArmoredInputStream(in);
//		OutputStream out = new BufferedOutputStream(new FileOutputStream(resultName));
//
//		//
//		// write out signed section using the local line separator.
//		// note: trailing white space needs to be removed from the end of
//		// each line RFC 4880 Section 7.1
//		//
//		ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
//		int                   lookAhead = readInputLine(lineOut, aIn);
//		byte[]                lineSep = getLineSeparator();
//
//		if (lookAhead != -1 && aIn.isClearText()) {
//			byte[] line = lineOut.toByteArray();
//			out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
//			out.write(lineSep);
//
//			while (lookAhead != -1 && aIn.isClearText()) {
//				lookAhead = readInputLine(lineOut, lookAhead, aIn);
//
//				line = lineOut.toByteArray();
//				out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
//				out.write(lineSep);
//			}
//		}
//
//		out.close();
//
//		PGPObjectFactory           pgpFact = new PGPObjectFactory(aIn);
//		PGPSignatureList           p3 = (PGPSignatureList)pgpFact.nextObject();
//		PGPSignature               sig = p3.get(0);
//
//		sig.initVerify(key, "BC");
//
//		//
//		// read the input, making sure we ignore the last newline.
//		//
//
//		InputStream sigIn = new BufferedInputStream(new FileInputStream(resultName));
//
//		lookAhead = readInputLine(lineOut, sigIn);
//
//		processLine(sig, lineOut.toByteArray());
//
//		if (lookAhead != -1) {
//			do {
//				lookAhead = readInputLine(lineOut, lookAhead, sigIn);
//
//				sig.update((byte)'\r');
//				sig.update((byte)'\n');
//
//				processLine(sig, lineOut.toByteArray());
//			}
//			while (lookAhead != -1);
//		}
//
//		sigIn.close();
//		return sig.verify();
//	}
	
	public static boolean verifyFile(InputStream in, PGPPublicKey key, String fname)
	throws Exception {
		in = PGPUtil.getDecoderStream(in);
		PGPObjectFactory pgpFact = new PGPObjectFactory(in);
		PGPOnePassSignatureList p1 = (PGPOnePassSignatureList)pgpFact.nextObject(); 
		PGPOnePassSignature ops = p1.get(0);
		PGPLiteralData p2 = (PGPLiteralData)pgpFact.nextObject();
		InputStream dIn = p2.getInputStream();
		int ch;
		FileOutputStream out = new FileOutputStream(fname);
		ops.initVerify(key, "BC");
		while ((ch = dIn.read()) >= 0) {
			ops.update((byte)ch);
			out.write(ch);
		}
		out.close(); 
		PGPSignatureList p3 = (PGPSignatureList)pgpFact.nextObject();
		return ops.verify(p3.get(0));
	}

	/**
	 * Get the first secret key contained in the list of keyrings of input that
	 * is suitable for signing.
	 * 
	 * Thanks to The Bouncy Castle Legion for this code.
	 * 
	 * @param input:
	 * 		An InputStream from where read the keyrings list.
	 * @return:
	 * 		A PGPSecretKey suitable for read.
	 * @throws IOException
	 * @throws PGPException
	 */
	// Avoid both iterators from give warnings.
	@SuppressWarnings("unchecked")
	public static PGPSecretKey recoverSecretKey(String key_path)
	throws IOException, PGPException {
		FileInputStream fis;
		PGPSecretKeyRingCollection pgpSec;
		Iterator keyring_iter;
		PGPSecretKeyRing keyRing;
		Iterator key_iter;
		PGPSecretKey key;

		// Take the list of keyrings.
		fis = new FileInputStream(key_path);
		pgpSec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(fis));

		// The Bouncy Castle Legion dixit:
		// *We just loop through the collection till we find a key suitable for
		// encryption, in the real world you would probably want to be a bit
		// smarter about this.* Right now i don't want to be smarter :D.
		keyring_iter = pgpSec.getKeyRings();
		while (keyring_iter.hasNext()) {
			keyRing = (PGPSecretKeyRing)keyring_iter.next();
			key_iter = keyRing.getSecretKeys();
			while (key_iter.hasNext()) {
				key = (PGPSecretKey)key_iter.next();
				if (key.isSigningKey())
					return key;
			}
		}

		throw new IllegalArgumentException("Can't find signing key in key ring.");
	}
	
	/**
	 * Returns the PGPPublicKey contained in the path passed.
	 * 
	 * @param key_path:
	 * 		The path to the file that has the public key.
	 * @return:
	 * 		A PGPPublicKey recovered from the file. Null if it cannot recover a
	 * 		key.
	 * @throws IOException 
	 * @throws PGPException 
	 */
	// Avoid both iterators from give warnings.
	@SuppressWarnings("unchecked")
	public static PGPPublicKey recoverPublicKey(String key_path)
	throws IOException, PGPException {
		PGPPublicKey pub_key;
		FileInputStream fis;
		InputStream is;
		PGPPublicKeyRingCollection keyring_list;
		Iterator keyring_iter;
		PGPPublicKeyRing keyring;
		Iterator key_iter;
		
		/** TODO: This has to be improved! why the first public key?! give
		 *  more flexibility here. **/
		// Get the first public key available in the keyring file passed.
		pub_key = null;
		fis = new FileInputStream(key_path);
		is = PGPUtil.getDecoderStream(fis);
		keyring_list = new PGPPublicKeyRingCollection(is);
		keyring_iter = keyring_list.getKeyRings();
		if (keyring_iter.hasNext()) {
			keyring = (PGPPublicKeyRing)keyring_iter.next();
			key_iter = keyring.getPublicKeys();
			if (key_iter.hasNext())
				pub_key = (PGPPublicKey)key_iter.next();
		}
		
		return pub_key;
	}
}
